/*           
Module : PSTAT.CPP
Purpose: Provides a generalised framework for executing a lengthy operation 
         in a thread. Feedback is provided by a progress dialog which can 
         optionally be cancelled.
Author : PJN / 27-03-1997 Initially created
History: PJN / 18-02-1998 Fixed a bug where the worker thread was trying to close a dialog 
                          before it was created. A CEvent member of CProgressThreadDlg now 
                          protects this. Also removed the usual appwizard comments from the 
                          demo program
         PJN / 8-11-1998  1. Added an option to confirm cancel by means of a message box.
                          2. General tidy up of the code
                          3. Inclusion of VC 5 workspace files now as standard
                          4. All code now compiles cleanly at warning level 4
                          5. Code now supports UNICODE and build configurations are now provided.
         PJN / 28-03-2000 1. Changed dialog resources which pstat uses to use the prefix ID._PSTAT_...
                          2. Update documentation about its usage.
         PJN / 30-03-2000 Fixed a race condition which existing in the worker thread. The issue was 
                          that the dialog could be closed before the CSingleLock destructor was 
                          called in the worker thread which resulted in an access violation
         PJN / 03-02-2003 1. Reimplemented a busy loop using a CEvent in ProgressDialogWorkerThread.
                          2. Implemented synchronous closure of the worker thread
                          3. Updated copyright details



Copyright (c) 1997 - 2003 by PJ Naughter.  (Web: www.naughter.com, Email: pjna@naughter.com)

All rights reserved.

Copyright / Usage Details:

You are allowed to include the source code in any product (commercial, shareware, freeware or otherwise) 
when your product is released in binary form. You are allowed to modify the source code in any way you want 
except you cannot modify the copyright details at the top of each module. If you want to distribute source 
code with your application, then you are only allowed to distribute versions released by the author. This is 
to maintain a single distribution point for the source code. 

*/

///////////////////////// Includes ///////////////////////////////////////////
#include "stdafx.h"
#include "resource.h"
#include "PStat.h"



///////////////////////// Macros / Defines ///////////////////////////////////

#ifdef _DEBUG
#define new DEBUG_NEW
#undef THIS_FILE
static char THIS_FILE[] = __FILE__;
#endif



////////////////////// Local Prototypes //////////////////////////////////////

//Use to pass a structure to the worker threads
class CProgressThreadInfo : public CObject                                                   
{
public:
//Constructors / Destructors
  CProgressThreadInfo();

//member variables
  CProgressThreadDlg*     m_pStatusDlg;          //Used to allow the UI to be updated during operation
  FUNCTION_WITH_PROGRESS* m_pfnFunction;         //Function to call to do the work
  void*                   m_pData;               //data to pass to the caller function
  CEvent*                 m_pDialogInitComplete; //Auto reset event which gets set when the dialog is been created

DECLARE_DYNAMIC(CProgressThreadInfo)
};

//The actual worker thread which is going to do the work
UINT ProgressDialogWorkerThread(LPVOID pParam);




/////////////////////// Implementation ////////////////////////////////////////

IMPLEMENT_DYNAMIC(CProgressThreadInfo, CObject)

CProgressThreadInfo::CProgressThreadInfo()
{
  m_pStatusDlg = NULL;
  m_pfnFunction = NULL;
  m_pData = NULL;
  m_pDialogInitComplete = NULL;
}



IMPLEMENT_DYNAMIC(CProgressThreadDlg, CDialog)

CProgressThreadDlg::CProgressThreadDlg(BOOL bShowCancelButton, BOOL bConfirmCancel, CWnd* pParent)
	: CDialog(bShowCancelButton ? IDD_PROGRESS : IDD_PROGRESS, pParent)
{
	//{{AFX_DATA_INIT(CProgressThreadDlg)
	//}}AFX_DATA_INIT
  m_nCurrentPercentage = 0;
  m_bReady = FALSE;
  m_bCancelled = FALSE;
  m_bShowCancelButton = bShowCancelButton;
  if (bConfirmCancel)
    ASSERT(m_bShowCancelButton);
  m_bConfirmCancel = bConfirmCancel;
  m_bOkToClose = FALSE;
  m_pThread = NULL;
  m_pDialogInitCompleted = NULL;
}

void CProgressThreadDlg::DoDataExchange(CDataExchange* pDX)
{
	CDialog::DoDataExchange(pDX);
	//{{AFX_DATA_MAP(CProgressThreadDlg)
	DDX_Control(pDX, IDC_PROGRESS, m_ctrlProgress);
	//}}AFX_DATA_MAP

	DDX_Control(pDX, IDC_STATIC_PROGRESS, ProgressMessageCtrl);
}

BEGIN_MESSAGE_MAP(CProgressThreadDlg, CDialog)
	//{{AFX_MSG_MAP(CProgressThreadDlg)	
	//}}AFX_MSG_MAP
	ON_MESSAGE (WM_FINISH_SEARCH, OnFinishSearch)
END_MESSAGE_MAP()

BOOL CProgressThreadDlg::OnInitDialog() 
{
  //Let the parent class do its thing
	CDialog::OnInitDialog();

  //initialise the progress control and remember the start time
	m_ctrlProgress.SetRange(0, 100);
  m_ctrlProgress.SetPos(0);
  m_TimeCreation = CTime::GetCurrentTime();
  m_bReady = TRUE;

  //Set the caption if need be
  if (!m_sCaption.IsEmpty())
    SetWindowText(m_sCaption);

  //Signal the event to signify that the dialog has initialized
  ASSERT(m_pDialogInitCompleted);
  m_pDialogInitCompleted->SetEvent();

	return TRUE;
}

void CProgressThreadDlg::OnCancel() 
{
  if (m_bOkToClose)
    CDialog::OnCancel();
  else if (m_bShowCancelButton)
  {
    if (m_bConfirmCancel)
    {
      //Worker thread needs to be suspended while we are displaying the confirm cancel message box
      ASSERT(m_pThread);
      m_pThread->SuspendThread();

      if (AfxMessageBox(m_sConfirmPrompt, MB_YESNO) == IDYES)
        m_bCancelled = TRUE;

      m_pThread->ResumeThread();
    }
    else
      m_bCancelled = TRUE;
  }
}

void CProgressThreadDlg::OnOK() 
{
  //deliberately do nothing
}

void CProgressThreadDlg::Close()
{
  m_bOkToClose = TRUE;
  PostMessage(WM_CLOSE);
}

void CProgressThreadDlg::OnCancelled() 
{
  OnCancel();
}

LRESULT CProgressThreadDlg::OnFinishSearch(UINT, LONG)
{
	AfxMessageBox("finished!");
	m_bCancelled = TRUE;
	return 0L;
}

void CProgressThreadDlg::SetPercentageDone(int Percentage) 
{
  if (!m_bReady || m_nCurrentPercentage == Percentage)
    return;

  m_ctrlProgress.SetPos(Percentage); 
  m_nCurrentPercentage = Percentage;

  if (Percentage)  //avoid division by 0
  {
    long lSecondsLeft = ((CTime::GetCurrentTime() - m_TimeCreation).GetTotalSeconds() * (100 - Percentage)) / Percentage;
    CTimeSpan TimeLeft = CTimeSpan(lSecondsLeft);

    int Minutes = TimeLeft.GetMinutes();
    int Seconds = TimeLeft.GetSeconds();

    CString sVal;
    CString sText;
    if (Minutes)
    {
      sVal.Format(_T("%d"), Minutes);
      AfxFormatString1(sText, TIMELEFT_IN_MINUTES, sVal);
    }
    else
    {
      if (Seconds)
      {
        sVal.Format(_T("%d"), Seconds);
        AfxFormatString1(sText, TIMELEFT_IN_SECONDS, sVal);
      }
      else 
      {
        //if 0 seconds are left then display no text
        sText = CString(_T(""));
      }
    }

 
  }
}

BOOL ExecuteFunctionWithProgressDialog(FUNCTION_WITH_PROGRESS* pfnFunction, const CString& sProgressTitle, 
                                       void* pData, DWORD dwFlags, const CString& sConfirmPrompt, int nPriority, CWnd* pParent)
{
  BOOL bShowCancel = (dwFlags & PSTAT_CANCEL);
  BOOL bConfirmCancel = (dwFlags & PSTAT_CONFIRMCANCEL);

  //Create the progress dialog and set its various flags
  CProgressThreadDlg dlg(bShowCancel, bConfirmCancel, pParent);
  dlg.m_sCaption = sProgressTitle;
  dlg.m_sConfirmPrompt = sConfirmPrompt;
  CProgressThreadInfo Info;
  Info.m_pStatusDlg = &dlg;
  Info.m_pfnFunction = pfnFunction;
  Info.m_pData = pData;
  CEvent DialogInitCompleted;
  Info.m_pDialogInitComplete = &DialogInitCompleted;
  dlg.m_pDialogInitCompleted = &DialogInitCompleted;

  //Create the worker thread (initally suspended)
  CWinThread* pThread = AfxBeginThread(ProgressDialogWorkerThread, &Info, nPriority, 0, CREATE_SUSPENDED);

  //Fail if the thread failed to create itself
  if (!pThread)
  {
    ASSERT(FALSE);
    return FALSE;
  }

  //We are in charge of the deletion of the thread
  pThread->m_bAutoDelete = FALSE; 

  //Store away the worker thread pointer in the dialog
  dlg.m_pThread = pThread;

  //Resume the wortker thread
  pThread->ResumeThread();

  //bring up the dialog modal (thread will close it for us)
  dlg.DoModal();


  //Wait for the worker thread to exit immediately
  ASSERT(pThread);
  WaitForSingleObject(pThread->m_hThread, INFINITE);
  delete pThread;

  return !dlg.HasBeenCancelled();
}

UINT ProgressDialogWorkerThread(LPVOID pParam)
{
  CProgressThreadInfo* pInfo = (CProgressThreadInfo*) pParam;
  ASSERT(pInfo);
  ASSERT(pInfo->IsKindOf(RUNTIME_CLASS(CProgressThreadInfo)));

  //Call the user defined function
  ASSERT(pInfo->m_pfnFunction);
  pInfo->m_pfnFunction(pInfo->m_pData, pInfo->m_pStatusDlg);

  //wait until the progress dialog is initialised before
  //we try to close it
  ASSERT(pInfo->m_pDialogInitComplete);
  WaitForSingleObject(*pInfo->m_pDialogInitComplete, INFINITE);

  //close down the progress dialog
  ASSERT(pInfo->m_pStatusDlg);
  pInfo->m_pStatusDlg->Close();

  return 0;
}

